package codingforgreat.class01;

import java.security.cert.X509Certificate;
import java.util.Arrays;

/*
 * 给定两个数组x和hp，长度都是N。
 * x数组一定是有序的，x[i]表示i号怪兽在x轴上的位置
 * hp数组不要求有序，hp[i]表示i号怪兽的血量
 * 为了方便起见，可以认为x数组和hp数组中没有负数。
 * 再给定一个正数range，表示如果法师释放技能的范围长度(直径！)
 * 被打到的每只怪兽损失1点血量。
 * 返回要把所有怪兽血量清空，至少需要释放多少次aoe技能？
 * 三个参数：int[] x, int[] hp, int range
 * 返回：int 次数
 * */
public class Class06_AOE {
    public static int minAoe1(int[] x, int[] hp, int range) {
    boolean isClean = true;
    int N = x.length;
        for (int i = 0; i < N; i++) {
            if(hp[i] > 0){
                isClean = false;
                break;
            }
        }
        if(isClean){
            return 0;
        }else {
            int ans = Integer.MAX_VALUE;
            for (int i = 0; i < N; i++) {
                if(hasHp(x, hp, i, range)){
                    minusOneHp(x, hp, i, range);
                    ans = Math.min(ans, minAoe1(x, hp, range) + 1);
                    addOneHp(x, hp, i, range);
                }
            }
            return ans;
        }
    }
    public static boolean hasHp(int[] x, int[] hp, int left, int range) {
        for (int index = left; index < x.length && x[index] - x[left] <= range; index++) {
            if (hp[index] > 0) {
                return true;
            }
        }
        return false;
    }

    public static void minusOneHp(int[] x, int[] hp, int left, int range) {
        for (int index = left; index < x.length && x[index] - x[left] <= range; index++) {
            hp[index]--;
        }
    }

    public static void addOneHp(int[] x, int[] hp, int left, int range) {
        for (int index = left; index < x.length && x[index] - x[left] <= range; index++) {
            hp[index]++;
        }
    }

    // 为了验证
    // 不用线段树，但是贪心的思路，和课上一样
    // 1) 总是用技能的最左边缘刮死当前最左侧的没死的怪物
    // 2) 然后向右找下一个没死的怪物，重复步骤1)
    public static int minAoe2(int[] x, int[] hp, int range) {
        // 举个例子：
        // 如果怪兽情况如下，
        // 怪兽所在，x数组  : 2 3 5 6 7 9
        // 怪兽血量，hp数组 : 2 4 1 2 3 1
        // 怪兽编号        : 0 1 2 3 4 5
        // 技能直径，range = 2
       int N = x.length;
       int[] cover = new int[N];
        // 首先求cover数组，
        // 如果技能左边界就在0号怪兽，那么技能到2号怪兽就覆盖不到了
        // 所以cover[0] = 2;
        // 如果技能左边界就在1号怪兽，那么技能到3号怪兽就覆盖不到了
        // 所以cover[1] = 3;
        // 如果技能左边界就在2号怪兽，那么技能到5号怪兽就覆盖不到了
        // 所以cover[2] = 5;
        // 如果技能左边界就在3号怪兽，那么技能到5号怪兽就覆盖不到了
        // 所以cover[3] = 5;
        // 如果技能左边界就在4号怪兽，那么技能到6号怪兽（越界位置）就覆盖不到了
        // 所以cover[4] = 6(越界位置);
        // 如果技能左边界就在5号怪兽，那么技能到6号怪兽（越界位置）就覆盖不到了
        // 所以cover[5] = 6(越界位置);
        // 综上：
        // 如果怪兽情况如下，
        // 怪兽所在，x数组  : 2 3 5 6 7 9
        // 怪兽血量，hp数组 : 2 4 1 2 3 1
        // 怪兽编号        : 0 1 2 3 4 5
        // cover数组情况   : 2 3 5 5 6 6
        // 技能直径，range = 2
        // cover[i] = j，表示如果技能左边界在i怪兽，那么技能会影响i...j-1号所有的怪兽
        // 就是如下的for循环，在求cover数组
        int r = 0;
        for (int i = 0; i < N; i++) {
            while (r < N &&  x[r] <= x[i] + range){
                r++;
            }
            cover[i] = r;
        }
            // 假设来到i号怪兽了
            // 如果i号怪兽的血量>0，说明i号怪兽没死
            // 根据我们课上讲的贪心：
            // 我们要让技能的左边界，刮死当前的i号怪兽
            // 这样能够让技能尽可能的往右释放，去尽可能的打掉右侧的怪兽
            // 此时cover[i]，正好的告诉我们，技能影响多大范围。
            // 比如当前来到100号怪兽，血量30
            // 假设cover[100] == 200
            // 说明，技能左边界在100位置，可以影响100号到199号怪兽的血量。
            // 为了打死100号怪兽，我们释放技能30次，
            // 释放的时候，100号到199号怪兽都掉血，30点
            // 然后继续向右寻找没死的怪兽，像课上讲的一样
        int res = 0;
        for (int i = 0; i < N; i++) {
            if(hp[i] > 0){
                int minus = hp[i];
                for (int index = i; index < cover[i]; index++) {
                    hp[index] -= minus;
                }
                res += minus;
            }
        }
        return res;

    }

    // 正式方法
    // 关键点就是：
    // 1) 线段树
    // 2) 总是用技能的最左边缘刮死当前最左侧的没死的怪物
    // 3) 然后向右找下一个没死的怪物，重复步骤2)
    public static int minAoe3(int[] x, int[] hp, int range) {
        int n = x.length;
        int[] cover = new int[n];
        int r = 0;
        // cover[i] : 如果i位置是技能的最左侧，技能往右的range范围内，最右影响到哪
        for (int i = 0; i < n; i++) {
            while (r < n && x[r] - x[i] <= range) {
                r++;
            }
            cover[i] = r - 1;
        }
        SegmentTree st = new SegmentTree(hp);
        st.build(1, n, 1);
        int ans = 0;
        for (int i = 1; i <= n; i++) {
            int leftHP = st.query(i, i, 1, n, 1);
            if (leftHP > 0) {
                ans += leftHP;
                st.add(i, cover[i - 1] + 1, -leftHP, 1, n, 1);
            }
        }
        return ans;
    }

    public static class SegmentTree {
        private int MAXN;
        private int[] arr;
        private int[] sum;
        private int[] lazy;

        public SegmentTree(int[] origin) {
            MAXN = origin.length + 1;
            arr = new int[MAXN];
            for (int i = 1; i < MAXN; i++) {
                arr[i] = origin[i - 1];
            }
            sum = new int[MAXN << 2];
            lazy = new int[MAXN << 2];
        }

        public void pushUp(int rt) {
            sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];
        }

        public void pushDown(int rt, int ln, int rn) {
            if (lazy[rt] != 0) {
                lazy[rt << 1] += lazy[rt];
                lazy[rt << 1 | 1] += lazy[rt];
                sum[rt << 1] += lazy[rt] * ln;
                sum[rt << 1 | 1] += lazy[rt] * rn;
                lazy[rt] = 0;
            }
        }

        public void build(int l, int r, int rt) {
            if(l==r){
                sum[rt]=arr[l];
                return;
            }else{
                int mid = (l + r) >> 1;
                build(l, mid, rt<<1);
                build(mid+1, r, rt<<1|1);
                pushUp(rt);
            }
        }
        public void add(int L,int R,int C,int l,int r,int rt){
            if(L <= l && r <= R){
                sum[rt] += C * (r - l + 1);
                lazy[rt] += C;
                return;
            }
            int mid = (l + r) / 2;
            pushDown(rt, mid - l + 1, r - mid);
            if(L <= mid){
                add(L, R, C, l, mid, rt<<1);
            }
            if(R > mid){
                add(L,R,C,mid + 1,r,rt<<1|1);
            }
            pushUp(rt);
        }
        public int query(int L,int R,int l,int r,int rt){
            if(L <= l && r <= R){
                return sum[rt];
            }else{
                int mid = (l + r) /2;
                pushDown(rt, mid - l + 1, r - mid);
                int res = 0;
                if(L <= mid){
                    res += query(L,R,l,mid,rt<<1);
                }
                if(R > mid){
                    res += query(L,R,mid + 1,r,rt << 1 | 1);
                }
                return res;
            }
        }
    }

    // 为了测试
    public static int[] randomArray(int n, int valueMax) {
        int[] ans = new int[n];
        for (int i = 0; i < n; i++) {
            ans[i] = (int) (Math.random() * valueMax) + 1;
        }
        return ans;
    }

    // 为了测试
    public static int[] copyArray(int[] arr) {
        int N = arr.length;
        int[] ans = new int[N];
        for (int i = 0; i < N; i++) {
            ans[i] = arr[i];
        }
        return ans;
    }

    // 为了测试
    public static void main(String[] args) {
        int N = 50;
        int X = 500;
        int H = 60;
        int R = 10;
        int testTime = 50000;
        System.out.println("测试开始");
        for (int i = 0; i < testTime; i++) {
            int len = (int) (Math.random() * N) + 1;
            int[] x2 = randomArray(len, X);
            Arrays.sort(x2);
            int[] hp2 = randomArray(len, H);
            int[] x3 = copyArray(x2);
            int[] hp3 = copyArray(hp2);
            int range = (int) (Math.random() * R) + 1;
            int ans2 = minAoe2(x2, hp2, range);
            int ans3 = minAoe3(x3, hp3, range);
            if (ans2 != ans3) {
                System.out.println("出错了！");
            }
        }
        System.out.println("测试结束");

        N = 500000;
        long start;
        long end;
        int[] x2 = randomArray(N, N);
        Arrays.sort(x2);
        int[] hp2 = new int[N];
        for (int i = 0; i < N; i++) {
            hp2[i] = i * 5 + 10;
        }
        int[] x3 = copyArray(x2);
        int[] hp3 = copyArray(hp2);
        int range = 10000;

        start = System.currentTimeMillis();
        System.out.println(minAoe2(x2, hp2, range));
        end = System.currentTimeMillis();
        System.out.println("运行时间 : " + (end - start) + " 毫秒");

        start = System.currentTimeMillis();
        System.out.println(minAoe3(x3, hp3, range));
        end = System.currentTimeMillis();
        System.out.println("运行时间 : " + (end - start) + " 毫秒");
    }

}
